// Copyright (c) Microsoft Corporation
// All rights reserved

namespace Microsoft.Windows.Controls.Specialized.TextModel
{
    using System;
    using System.Text;
    using System.Diagnostics;
    using System.Collections;
    using System.Collections.Generic;

    public class LinkBuffer : TextBuffer
    {
        // User data
        private Dictionary<object, object> _values;

        #region MoveResult Struct

        internal struct MoveResult
        {
            // TODO:  Come up with a better name for these members
            public bool complete;
            public int result;

            /// <summary>
            /// Constructs a move result structure to return after any move operation
            /// </summary>
            public MoveResult(bool complete, int result)
            {
                this.complete = complete;
                this.result = result;
            }
        }

        #endregion // MoveResult Struct

        #region TextBlock Class

        internal class TextBlock
        {
            #region Private Members

            /// <summary>
            /// Private members
            /// </summary>
            private StringBuilder _sb;
            private int _wholeLineCount;

            // We use a threshold to keep the buffer efficient.  The block will not request a split until
            // the maxThreshold is reached.  But once the maxThreshold is triggered, the block will demand 
            // to be split until the optimalThreshold is reached.
            internal const int _maxThreshold = 4096, _optimalThreshold = 2048;
            private bool _thresholdTrigger;

            #endregion // Private Members

            /// <summary>
            /// Create a new Text Block and initializes the internal string buffer
            /// </summary>
            public TextBlock()
            {
                _sb = new StringBuilder();
                _wholeLineCount = 0;
                _thresholdTrigger = false;
            }

            /// <summary>
            /// Updates the internal line count.  The line count is the count of full lines.  Full lines are 
            /// lines that end with "\r\n" or "\n" or in certain weird cases, "\r"
            /// </summary>
            public void ComputeLineCount()
            {
                _wholeLineCount = LinkBuffer.ScanForLineCount(_sb);
            }

            /// <summary>
            /// Inserts the specified string of text to the internal buffer, at the specified index
            /// </summary>
            /// <remarks>
            /// This method will not update the line count.  The caller is expected to update the line count
            /// for the block.  The reason is, the line count could be optimized away or could already be
            /// coming from an external source
            /// </remarks>
            public void InsertText(int index, string text)
            {
                _sb.Insert(index, text);
            }

            /// <summary>
            /// Inserts the specified character array into the internal buffer, at the specified index
            /// </summary>
            /// <remarks>
            /// This method will not update the line count.  The caller is expected to update the line count
            /// for the block.  The reason is, the line count could be optimized away or could already be
            /// coming from an external source
            /// </remarks>
            public void InsertText(int index, char[] data)
            {
                _sb.Insert(index, data);
            }

            /// <summary>
            /// Deletes the specified length of characters from the specified start index
            /// </summary>
            /// <remarks>
            /// This method will not update the line count.  The caller is expected to update the line count
            /// for the block.  The reason is, the line count could be optimized away or could already be
            /// coming from an external source
            /// </remarks>
            public void DeleteText(int startIndex, int length)
            {
                _sb.Remove(startIndex, length);
            }

            /// <summary>
            /// Gets the specified length of text from the start index
            /// </summary>
            public string GetText(int startIndex, int length)
            {
                return _sb.ToString(startIndex, length);
            }

            /// <summary>
            /// Get a specific character from the text.
            /// </summary>
            public char GetChar(int index) 
            {
                if (index >= _sb.Length)
                    return '\0';
                return _sb[index];
            }

            /// <summary>
            /// Moves to the next line from the given position.  
            /// </summary>
            /// <returns>
            /// The position at the start of the next line
            /// </returns>
            public MoveResult MoveToNextLine(int position)
            {
                bool moveSucceeded = false;

                // Special case optimization
                if (_wholeLineCount == 0)
                    return new MoveResult(moveSucceeded, this.Length);

                while (position < _sb.Length)
                {
                    char c = _sb[position++];
                    if (c == '\n')
                    {
                        moveSucceeded = true;
                        break;
                    }
                    else if (c == '\r')
                    {
                        moveSucceeded = true;
                        if (position < _sb.Length && _sb[position] == '\n')
                            position++;
                        break;
                    }
                }

                return new MoveResult(moveSucceeded, position);
            }

            /// <summary>
            /// Moves to the previous line from the given position.  
            /// </summary>
            /// <returns>
            /// The position at the start of the previous line
            /// </returns>
            /// <remarks>
            /// This method will move to the end of the previous line.  You should call the Move to the 
            /// Start of the line method in conjunction with this method
            /// </remarks>
            public MoveResult MoveToPreviousLine(int position)
            {
                bool moveSucceeded = false;

                // Special case optimization
                if (_wholeLineCount == 0)
                    return new MoveResult(moveSucceeded, 0);

                // Check if we're on a carriage return or line feed
                if (position == _sb.Length)
                    position--;
                else
                {
                    char c = _sb[position--];
                    if (c == '\n')
                    {
                        if (position >= 0 && _sb[position] == '\r')
                            position--;
                    }
                }

                // Now move to the end of the previous line
                while (position >= 0)
                {
                    char c = _sb[position--];
                    if (c == '\n')
                    {
                        moveSucceeded = true;
                        if (position >= 0 && _sb[position] == '\r')
                            position--;
                        break;
                    }
                    else if (c == '\r')
                    {
                        moveSucceeded = true;
                        break;
                    }
                }

                return new MoveResult(moveSucceeded, position + 1);
            }

            /// <summary>
            /// Moves to the start of the current line
            /// </summary>
            /// <remarks>
            /// NOTE: This is not going to work if the current position is inbetween '\r' and '\n'
            /// </remarks>
            public MoveResult MoveToStartOfLine(int position)
            {
                bool moveSucceeded = false;

                // Special case optimization
                if (_wholeLineCount == 0)
                    return new MoveResult(moveSucceeded, 0);

                // Check if we're on a carriage return or line feed
                if (position == _sb.Length)
                    position--;
                else
                {
                    char c = _sb[position--];
                    if (c == '\n')
                    {
                        if (position >= 0 && _sb[position] == '\r')
                            position--;
                    }
                }

                while (position >= 0)
                {
                    char c = _sb[position];
                    if (c == '\r' || c == '\n')
                    {
                        moveSucceeded = true;
                        break;
                    }
                    position--;
                }

                return new MoveResult(moveSucceeded, position + 1);
            }

            /// <summary>
            /// Gets the length of the line starting from the specified position
            /// </summary>
            public MoveResult GetLengthOfLine(int position)
            {
                bool moveSucceeded = false;

                // Special case optimization
                if (WholeLineCount == 0)
                    return new MoveResult(moveSucceeded, this.Length - position);

                int lineLength = 0;
                while (position < _sb.Length)
                {
                    char c = _sb[position++];
                    if (c == '\r' || c == '\n')
                    {
                        moveSucceeded = true;
                        break;
                    }
                    lineLength++;
                }

                return new MoveResult(moveSucceeded, lineLength);
            }

            /// <summary>
            /// Strips portion of text from the end of this block.  The amount of text stripped is capped* by
            /// the specified "available space".
            /// </summary>
            /// <param name="availableSpace">
            /// How much of the text needs to be stripped.  This is the max* amount of text that can be 
            /// stripped (Check Remarks)
            /// </param>
            /// <param name="strippedText">
            /// Sends out the actual text that was stripped from this block
            /// </param>
            /// <returns>
            /// The length of the stripped text
            /// </returns>
            /// <remarks>
            /// The stripped text length can be one more than the available space.  The reason for this is
            /// we try to keep '\r\n' together in cases where they might fall at the boundary. 
            /// 
            /// However, we could have returned one character less.  However, in extreme cases, like when the
            /// block is exactly full and a new block was created, we would take the min-threshold - 1 amount of
            /// bytes and that would require another split which means, we'll have a new block with just two
            /// characters.
            /// </remarks>
            public int StripText(int availableSpace, out string strippedText)
            {
                if (_sb[this.Length - availableSpace] == '\n')
                    availableSpace--;

                strippedText = _sb.ToString(this.Length - availableSpace, availableSpace);
                _sb.Remove(this.Length - availableSpace, availableSpace);
                return availableSpace;
            }

            /// <summary>
            /// Gets the length of the string buffer contained by this text block
            /// </summary>
            public int Length
            {
                get
                {
                    return _sb.Length;
                }
            }

            /// <summary>
            /// Gets or sets the whole line count for the string buffer contained by this text block
            /// </summary>
            public int WholeLineCount
            {
                get
                {
                    return _wholeLineCount;
                }

                set
                {
                    _wholeLineCount = value;
                }
            }

            /// <summary>
            /// Specifies whether or not this block of code is in need of a split.
            /// </summary>
            public bool NeedsSplit
            {
                get
                {
                    int compareAgainst = _maxThreshold;

                    if (_thresholdTrigger)
                    {
                        // If the threshold trigger had been set, we need to make sure that the buffer length
                        // falls below the optimal threshold
                        compareAgainst = _optimalThreshold;
                    }

                    if (_sb.Length > compareAgainst)
                    {
                        _thresholdTrigger = true;
                        return true;
                    }
                    else
                    {
                        _thresholdTrigger = false;
                        return false;
                    }
                }
            }

            /// <summary>
            /// Gets the amount of space available in this block for further insertions (may be negative)
            /// </summary>
            public int AvailableSpace
            {
                get
                {
                    return _optimalThreshold - _sb.Length;
                }
            }
        }

        #endregion

        #region Edit Class Definition

        private class Edit : ITextEdit
        {
            /// <summary>
            /// Private members
            /// </summary>
            private LinkBuffer _textBuffer;
            private ChangeNode _originNode;
            private int _bufferLength;
            private List<TextChange> _changes;
            private ITextReader _frozenBuffer;

            public Edit(LinkBuffer buffer)
            {
                _textBuffer = buffer;
                _originNode = buffer._currentChange;
                _bufferLength = buffer.Length;
                _changes = new List<TextChange>();
                _frozenBuffer = null;
            }

            /// <summary>
            /// Inserts the given text in the specified location
            /// </summary>
            /// <param name="position">
            /// The position at which the given text is to be inserted
            /// </param>
            /// <param name="text">
            /// The text to be inserted
            /// </param>
            public void Insert(int position, string text)
            {
                if (position < 0 || position > _bufferLength)
                    throw new ArgumentOutOfRangeException("position");

                if (text == null)
                    throw new ArgumentNullException("text");

                _changes.Add(new TextChange(position, "", text));
            }

            /// <summary>
            /// Inserts an array of characters in the specified location
            /// </summary>
            /// <param name="position">
            /// The position at which the given text is to be inserted
            /// </param>
            /// <param name="buffer">
            /// The character array from which characters will be inserted
            /// </param>
            /// <param name="startIndex">
            /// The starting index of the text from the buffer
            /// </param>
            /// <param name="length">
            /// The length of text to insert
            /// </param>
            public void Insert(int position, char[] buffer, int startIndex, int length)
            {
                if (position < 0 || position > _bufferLength)
                    throw new ArgumentOutOfRangeException("position");

                if (buffer == null)
                    throw new ArgumentNullException("buffer");

                if (startIndex < 0 || startIndex > buffer.Length)
                    throw new ArgumentOutOfRangeException("startIndex");

                if (length < 0 || startIndex + length > buffer.Length)
                    throw new ArgumentOutOfRangeException("length");

                _changes.Add(new TextChange(position, "",
                                             new string(buffer, startIndex, length)));
            }

            /// <summary>
            /// Replaces a range of characters with a some new text
            /// </summary>
            /// <param name="startPosition">
            /// The position to start replacing from
            /// </param>
            /// <param name="charsToDelete">
            /// The number of characters to replace
            /// </param>
            /// <param name="replaceWith">
            /// The new text to replace the old
            /// </param>
            public void Replace(int startPosition, int lengthToDelete, string replaceWith)
            {
                if (startPosition < 0 || startPosition > _bufferLength)
                    throw new ArgumentOutOfRangeException("startPosition");

                if (lengthToDelete < 0 || startPosition + lengthToDelete > _bufferLength)
                    throw new ArgumentOutOfRangeException("charsToDelete");

                if (_frozenBuffer == null)
                    _frozenBuffer = _textBuffer.Freeze(_originNode);

                _changes.Add(new TextChange(startPosition, _frozenBuffer.GetText(startPosition,
                                                                   lengthToDelete), replaceWith));
            }

            /// <summary>
            /// Deletes a range of characters from the underlying buffer
            /// </summary>
            /// <param name="startPosition">
            /// The position to start deleting from
            /// </param>
            /// <param name="charsToDelete">
            /// The number of characters to delete
            /// </param>
            public void Delete(int startPosition, int lengthToDelete)
            {
                if (startPosition < 0 || startPosition > _bufferLength)
                    throw new ArgumentOutOfRangeException("startPosition");

                if (lengthToDelete < 0 || startPosition + lengthToDelete > _bufferLength)
                    throw new ArgumentOutOfRangeException("charsToDelete");

                if (_frozenBuffer == null)
                    _frozenBuffer = _textBuffer.Freeze(_originNode);

                _changes.Add(new TextChange(startPosition, _frozenBuffer.GetText(startPosition,
                                                                   lengthToDelete), ""));
            }

            /// <summary>
            /// Commits all the transactions to the underlying buffer
            /// </summary>
            [Obsolete("Please use Apply() method.", false)]
            public void Commit()
            {
                Apply();
            }

            public void Apply()
            {
                if (_changes.Count > 0)
                    _textBuffer.ApplyChanges(this, _changes, _originNode);
            }
        }

        #endregion // Edit Class Definition

        #region ChangeNode Class Definition

        /// <summary>
        /// A private implementation of ITextVersion
        /// </summary>
        class ChangeNode : ITextVersion
        {
            private ChangeNode _next;
            private TextChange _change;
            private static int _counter = 0;
            private int _versionIndex;

            public ChangeNode()
            {
                _next = null;
                _change = null;
                _versionIndex = _counter++;
            }

            /// <summary>
            /// Attaches the change to the current node and creates the next version node
            /// </summary>
            public ChangeNode CreateNext(TextChange change)
            {
                _change = change;
                _next = new ChangeNode();
                return _next;
            }

            /// <summary>
            /// Gets the next version node
            /// </summary>
            public ITextVersion Next
            {
                get
                {
                    return _next;
                }
            }

            /// <summary>
            /// Gets the current change information
            /// </summary>
            public TextChange Change
            {
                get
                {
                    return _change;
                }
            }

            /// <summary>
            /// Gets an index of the text version
            /// </summary>
            public int VersionIndex
            {
                get
                {
                    return _versionIndex;
                }
            }
        }

        #endregion // ChangeNode Class Definition

        #region SplitDeletion Class Definition

        /// <summary>
        /// Describes a deletion that is split into two because of a transactional change
        /// </summary>
        class SplitDeletion
        {
            #region Private Members

            private ITextVersion _updateStart;
            private TextChange _newDeletion;

            #endregion // Private Members

            public SplitDeletion(ITextVersion updateStart, TextChange newDeletion)
            {
                _updateStart = updateStart;
                _newDeletion = newDeletion;
            }

            /// <summary>
            /// Gets the text version for this change
            /// </summary>
            public ITextVersion UpdateStart
            {
                get
                {
                    return _updateStart;
                }
            }

            /// <summary>
            /// Gets the new change that was created because of the split
            /// </summary>
            public TextChange NewDeletion
            {
                get
                {
                    return _newDeletion;
                }
            }
        }

        #endregion // SplitDeletion Class Definition

        #region Private Members

        /// <summary>
        /// Private members
        /// </summary>
        internal List<TextBlock> _blockList;
        private int _lineCount, _length;
        internal int _currentLine, _currentBlock, _currentPosition, _currentBlockPosition;
        private ChangeNode _currentChange;
        private const int _maxDistanceForRelativeMove = 4096;
        private const int _maxLinesForRelativeMove = 100;

        #endregion // Private Members

        /// <summary>
        /// Constructs a Link Buffer
        /// 
        /// We keep the constructor internal so that the link buffer can only be created via the Link Buffer
        /// Service
        /// </summary>
        public LinkBuffer()
        {
            // Initialize the block list.  In the beginning there is one block.  This is the 
            // minimum number of blocks there will be.  
            _blockList = new List<TextBlock>();
            TextBlock firstBlock = new TextBlock();
            _blockList.Add(firstBlock);

            // There will always be at least one line
            _lineCount = 1;

            _length = _currentLine = _currentBlock = _currentPosition = _currentBlockPosition = 0;
            _currentChange = new ChangeNode();
        }

        /// <summary>
        /// Constructs a Link Buffer
        /// 
        /// We keep the constructor internal so that the link buffer can only be created via the Link Buffer
        /// Service
        /// </summary>
        /// <param name="documentType">
        /// The type of the document represented by the text contained in this buffer
        /// </param>
        public LinkBuffer(string documentType) : this()
        {
            // Verify
            Debug.Assert(documentType != null);

            this.DocumentType = documentType;            
        }

        #region ITextBuffer Members

        /// <summary>
        /// Computes the position of the start of the given line.
        /// </summary>
        public override int GetStartOfLine(int line)
        {
            // Validate
            if (line < 0 || line >= _lineCount)
                throw new ArgumentOutOfRangeException("line");

            SetCurrentLine(line);
            MoveToStartOfLine();
            return _currentPosition;
        }

        /// <summary>
        /// Gets the length of the specified line
        /// </summary>
        public override int GetLengthOfLine(int line)
        {
            // Validate
            if (line < 0 || line >= _lineCount)
                throw new ArgumentOutOfRangeException("line");

            SetCurrentLine(line);
            MoveToStartOfLine();

            // Note: We don't move the current position for measuring the length of line
            int totalLength = 0;
            int block = _currentBlock;
            int blockPosition = _currentBlockPosition;

            while (true)
            {
                TextBlock textBlock = _blockList[block];
                MoveResult result = textBlock.GetLengthOfLine(blockPosition);
                totalLength += result.result;

                if (!result.complete &&
                    block < _blockList.Count - 1)
                {
                    // The line didn't end with this block
                    block++;
                    blockPosition = 0;
                }
                else
                {
                    break;
                }
            }

            return totalLength;
        }

        /// <summary>
        /// Gets the start position of the line that contains the character at the specified position
        /// </summary>
        public override int GetStartOfLineContaining(int position)
        {
            // Validate
            if (position < 0 || position > _length)
                throw new ArgumentOutOfRangeException("position");

            MoveToLineAtPosition(position);
            return _currentPosition;
        }

        /// <summary>
        /// Gets the end position of the line that contains the character at the specified position
        /// </summary>
        public override int GetEndOfLineContaining(int position)
        {
            // Validate
            if (position < 0 || position > _length)
                throw new ArgumentOutOfRangeException("position");

            MoveToLineAtPosition(position);
            return _currentPosition + GetLengthOfLine(_currentLine);
        }

        /// <summary>
        /// Gets the start position of the line next to the line that contains the character at the specified
        /// position
        /// </summary>
        public override int GetStartOfNextLine(int position)
        {
            // Validate
            if (position < 0 || position > _length)
                throw new ArgumentOutOfRangeException("position");

            MoveToLineAtPosition(position);
            MoveToNextLine();
            return _currentPosition;
        }

        /// <summary>
        /// Gets the start position of the line before the line that contains the character at the specified 
        /// position
        /// </summary>
        public override int GetStartOfPreviousLine(int position)
        {
            // Validate
            if (position < 0 || position > _length)
                throw new ArgumentOutOfRangeException("position");

            MoveToLineAtPosition(position);
            MoveToPreviousLine();
            return _currentPosition;
        }

        /// <summary>
        /// Gets the line number that contains the character at the specified location
        /// </summary>
        public override int GetLineContaining(int position)
        {
            // Validate
            if (position < 0 || position > _length)
                throw new ArgumentOutOfRangeException("position");

            MoveToLineAtPosition(position);
            return _currentLine;
        }

        /// <summary>
        /// Gets the offset of the character from the start of its line
        /// </summary>
        public override int GetColumnOffset(int position)
        {
            // Validate
            if (position < 0 || position > _length)
                throw new ArgumentOutOfRangeException("position");

            MoveToLineAtPosition(position);
            return position - _currentPosition;
        }

        /// <summary>
        /// Gets the TextLine given the line number
        /// </summary>
        public override BufferLine GetTextLine(int lineNumber)
        {
            if (lineNumber < 0 || lineNumber > this.LineCount)
                throw new ArgumentOutOfRangeException("lineNumber");

            int start = GetStartOfLine(lineNumber);
            int endOfLine = GetEndOfLineContaining(start);
            int startOfNextLine = GetStartOfNextLine(endOfLine);
            int length = endOfLine - start;
            int newLineLength = Math.Max(startOfNextLine - endOfLine, 0);
            return new BufferLine(new TextPoint(this, this.Version, start, TrackingMode.Positive), length, newLineLength);
        }

        /// <summary>
        /// Inserts text at the specified position
        /// </summary>
        public override void Insert(int position, string text)
        {
            // Validate
            if (position < 0 || position > _length)
                throw new ArgumentOutOfRangeException("position");

            if (text == null)
                throw new ArgumentNullException("text");

            ITextEdit edit = this.CreateEdit();

            edit.Insert(position, text);
            edit.Apply();
        }

        /// <summary>
        /// Deletes a specified number of characters at the start position specified
        /// </summary>
        public override void Delete(int startPosition, int charsToDelete)
        {
            // Validate
            if (startPosition < 0 || startPosition > _length)
                throw new ArgumentOutOfRangeException("startPosition");

            if (charsToDelete < 0 || startPosition + charsToDelete > _length)
                throw new ArgumentOutOfRangeException("charsToDelete");

            ITextEdit edit = this.CreateEdit();

            edit.Delete(startPosition, charsToDelete);
            edit.Apply();
        }

        /// <summary>
        /// Replaces a given range of characters with the specified string
        /// </summary>
        public override void Replace(int startPosition, int charsToDelete, string replaceWith)
        {
            // Validate
            if (startPosition < 0 || startPosition > _length)
                throw new ArgumentOutOfRangeException("startPosition");

            if (charsToDelete < 0 || startPosition + charsToDelete > _length)
                throw new ArgumentOutOfRangeException("charsToDelete");

            if (replaceWith == null)
                throw new ArgumentNullException("replaceWith");

            ITextEdit edit = this.CreateEdit();

            edit.Replace(startPosition, charsToDelete, replaceWith);
            edit.Apply();
        }

        /// <summary>
        /// Freezes the buffer at the specified version, to be able to read from, without having to worry
        /// about text changes happening simultaneously
        /// </summary>
        public override ITextReader Freeze(ITextVersion version)
        {
            // Validation
            if (version == null)
                throw new ArgumentNullException("version");

            return new FrozenBuffer(this, version);
        }

        /// <summary>
        /// Creates a transactional edit class, that implements ITextEdit
        /// </summary>
        public override ITextEdit CreateEdit()
        {
            return new Edit(this);
        }

        /// <summary>
        /// An event to indicate a change in the text buffer
        /// </summary>
        public override event TextChangedEventHandler Changed;

        #endregion

        #region ITextReader Members

        /// <summary>
        /// Gets the specified length of text from the start index
        /// </summary>
        public override string GetText(int startIndex, int length)
        {
            // Validate
            if (startIndex < 0 || startIndex > _length)
                throw new ArgumentOutOfRangeException("startIndex");

            if (length < 0 || startIndex + length > _length)
                throw new ArgumentOutOfRangeException("length");

            MoveToPosition(startIndex);

            // Calculate the relative start index for the block
            int startRead = _currentBlockPosition + startIndex - _currentPosition;
            int readLength;

            // Create a local string builder to append as we read from blocks
            StringBuilder sb = new StringBuilder();
            int blockToRead = _currentBlock;

            // Do we have more characters to read?
            while (length > 0)
            {
                readLength = Math.Min(_blockList[blockToRead].Length - startRead, length);
                sb.Append(_blockList[blockToRead].GetText(startRead, readLength));

                // Decrement the number of characters to read, by the amount already read
                length -= readLength;
                blockToRead++;

                // When we move to the next block to read, we always start reading from the start of the 
                // block
                startRead = 0;
            }

            return sb.ToString();
        }

        /// <summary>
        /// Gets text from the specified start index to the end
        /// </summary>
        public override string GetText(int startIndex)
        {
            return GetText(startIndex, _length - startIndex);
        }

        /// <summary>
        /// Gets the complete text
        /// </summary>
        public override string GetText()
        {
            return GetText(0, _length);
        }

        /// <summary>
        /// Get a character at the specified index
        /// </summary>
        public override char GetChar(int index) 
        {
            // Validate
            if (index < 0 || index > _length)
                throw new ArgumentOutOfRangeException("index");

            MoveToPosition(index);

            // Calculate the relative start index for the block
            int blockIndex = _currentBlockPosition + index - _currentPosition;

            // Return the character
            return _blockList[_currentBlock].GetChar(blockIndex);
        }

        /// <summary>
        /// Converts the specified length of text at the start Index to a character array.
        /// </summary>
        public override char[] ToCharArray(int startIndex, int length)
        {
            return GetText(startIndex, length).ToCharArray();
        }

        /// <summary>
        /// Converts the entire text to a character array
        /// </summary>
        public override char[] ToCharArray()
        {
            return GetText().ToCharArray();
        }

        /// <summary>
        /// Copies the text to a character array
        /// </summary>
        public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex,
                            int count)
        {
            string ret = GetText(sourceIndex, count);

            ret.CopyTo(sourceIndex, destination, destinationIndex, count);
        }

        /// <summary>
        /// Gets the length of the entire buffer
        /// </summary>
        public override int Length
        {
            get
            {
                return _length;
            }
        }

        /// <summary>
        /// Gets the line count of the entire buffer
        /// </summary>
        public override int LineCount
        {
            get
            {
                return _lineCount;
            }
        }

        /// <summary>
        /// Gets the current version of the buffer
        /// </summary>
        public override ITextVersion Version
        {
            get
            {
                return _currentChange;
            }
        }

        #endregion // ITextReader Members

        #region Public Helpers

        /// <summary>
        /// Reads data from the specified stream and builds the text buffer from it
        /// </summary>
        /// <param name="reader">
        /// The stream to read from
        /// </param>
        /// <remarks>
        /// This operation will clear all the existing blocks and clear all version history.  This means, the
        /// buffer will not be able to recover from this operation.
        /// </remarks>
        public void ReadFrom(System.IO.StreamReader reader)
        {
            // TODO: This method should be revisited.  We haven't considered all encoding here
            // Validate
            if (reader == null)
                throw new ArgumentNullException("stream");
            
            char[] chars = new char[TextBlock._optimalThreshold + 1];

            // Clean up the existing blocks & version history
            _blockList.Clear();
            _currentChange = new ChangeNode();

            // We need to start from the beginning
            ResetCachedPositions();

            int nextBlockOffset = 0;
            while (true)
            {
                TextBlock textBlock = new TextBlock();

                int read = reader.ReadBlock(chars, nextBlockOffset, chars.Length - nextBlockOffset);
                int charsToWrite = read + nextBlockOffset;

                if (read == chars.Length - nextBlockOffset)
                {
                    // Check if this block of text ends with a '\r\n'.  If it does, we'd insert the entire
                    // block, else, we'll insert the block less one character.
                    if (chars[TextBlock._optimalThreshold] == '\n' ||
                        chars[TextBlock._optimalThreshold - 1] == '\r')
                    {
                        nextBlockOffset = 0;
                    }
                    else
                    {
                        charsToWrite--;
                        nextBlockOffset = 1;
                    }
                }
                else if (read == 0)
                {
                    if (nextBlockOffset != 0)
                    {
                        AppendTextInto(textBlock, chars, charsToWrite);
                        _blockList.Add(textBlock);
                    }

                    if (_blockList.Count == 0)
                    {
                        // We need at least one text block
                        _blockList.Add(textBlock);
                    }

                    break;
                }
                else
                {
                    nextBlockOffset = 0;
                }

                AppendTextInto(textBlock, chars, charsToWrite);
                _blockList.Add(textBlock);

                // Keep the last character as the beginning character of the next block
                if (nextBlockOffset == 1)
                    chars[0] = chars[TextBlock._optimalThreshold];
            }
        }

        #endregion // Public Helpers

        #region Private Helpers

        /// <summary>
        /// Appends the given text into the specified text block
        /// </summary>
        /// <param name="textBlock">
        /// The text block to append the text to
        /// </param>
        /// TODO: (ErikNe) textToInsert is the incorrect name; length param is missing
        /// <param name="textToInsert">
        /// The text to be inserted into the given text block
        /// </param>
        private void AppendTextInto(TextBlock textBlock, char[] chars, int length)
        {
            int lines = ScanForLineCount(chars, length);
            if (chars.Length == length)
            {
                textBlock.InsertText(textBlock.Length, chars);
            }
            else
            {
                string text = new string(chars, 0, length);
                textBlock.InsertText(textBlock.Length, text);
            }

            textBlock.WholeLineCount += lines;

            // Update the buffer as whole
            _lineCount += lines;
            _length += length;
        }

        /// <summary>   
        /// Sets the current line and its associated position
        /// </summary>
        /// <param name="line">
        /// The line to move to.
        /// </param>
        private void SetCurrentLine(int line)
        {
            if (Math.Abs(line - _currentLine) > _maxLinesForRelativeMove)
            {
                // We do an absolute move
                _currentLine = _currentBlock = 0;
                _currentBlockPosition = _currentPosition = 0;

                int totalBlockCount = _blockList.Count;
                while (_currentBlock < totalBlockCount - 1)
                {
                    TextBlock textBlock = _blockList[_currentBlock];
                    _currentBlock++;
                    _currentLine += textBlock.WholeLineCount;
                    _currentPosition += textBlock.Length;

                    if (_currentLine >= line)
                        break;
                }
            }

            // This method is private and will always be called from within Link Buffer and
            // so, we don't need to validate the argument.
            while (line > _currentLine)
            {
                // Scan forward from the current line
                MoveToNextLine();
            }

            while (line < _currentLine)
            {
                // Scan backward from the current line
                MoveToPreviousLine();
            }
        }

        /// <summary>
        /// Moves the current pointer to the line that has the given position
        /// </summary>
        private void MoveToLineAtPosition(int position)
        {
            // Determine if we need to do an absolute move before proceeding with the relative move
            if (Math.Abs(_currentPosition - position) > _maxDistanceForRelativeMove)
            {
                // We do an absolute move
                _currentLine = _currentBlock = 0;
                _currentBlockPosition = _currentPosition = 0;

                int totalBlockCount = _blockList.Count;
                while (_currentBlock < totalBlockCount - 1)
                {
                    TextBlock textBlock = _blockList[_currentBlock];
                    _currentBlock++;
                    _currentLine += textBlock.WholeLineCount;
                    _currentPosition += textBlock.Length;

                    if (_currentPosition > position)
                        break;
                }
            }

            if (_currentPosition < position)
            {
                while (_currentPosition < position)
                {
                    // Move to Next line can fail if it's already on the last line
                    if (!MoveToNextLine())
                    {
                        MoveToStartOfLine();
                        return;
                    }
                }

                // At this point, if we're not at the specified position, we've clearly over-shot
                if (_currentPosition != position)
                    MoveToPreviousLine();
            }
            else
            {
                MoveToStartOfLine();

                while (_currentPosition > position)
                {
                    // Move to Next line can fail if it's already on the first line
                    if (!MoveToPreviousLine())
                        return;
                }
            }
        }

        /// <summary>
        /// Moves the current pointer to the specified position
        /// </summary>
        /// <param name="position">
        /// The character offset to move to
        /// </param>
        private void MoveToPosition(int position)
        {
            // Check if we're already in the current position.  
            if (position == _currentPosition)
                return;

            // First move to the start of the line
            MoveToLineAtPosition(position);

            int delta = position - _currentPosition;
            while (delta > 0)
            {
                if (_currentBlockPosition + delta > _blockList[_currentBlock].Length)
                {
                    // We need to move to the next block
                    Debug.Assert(_currentBlock < _blockList.Count - 1);

                    int currentMove = _blockList[_currentBlock].Length - _currentBlockPosition;

                    // Move to the next block
                    AdvanceBlock();

                    _currentPosition += currentMove;
                    delta -= currentMove;
                }
                else
                {
                    int currentMove = delta;
                    _currentBlockPosition += currentMove;
                    _currentPosition += currentMove;
                    delta -= currentMove;

                    // If we're at the end of a block, move to the start of the next block
                    if (_currentBlockPosition == _blockList[_currentBlock].Length &&
                        _currentBlock < _blockList.Count - 1)
                    {
                        _currentBlock++;
                        _currentBlockPosition = 0;
                    }
                }
            }

            // Validate
            Debug.Assert(position == _currentPosition);
        }

        /// <summary>
        /// Moves to the next line from the current position
        /// </summary>
        private bool MoveToNextLine()
        {
            if (_currentLine == _lineCount - 1)
                return false;

            int totalBlockCount = _blockList.Count;

            while (true)
            {
                TextBlock textBlock = _blockList[_currentBlock];

                MoveResult result = textBlock.MoveToNextLine(_currentBlockPosition);
                _currentPosition += (result.result - _currentBlockPosition);
                _currentBlockPosition = result.result;

                // If the move did not complete, it is because this block ended in a 
                // partial line.  If another block follows this, we need to keep going
                // to get to the beginning of the next line.  Otherwise, we can't, so quit.
                if (!result.complete)
                {
                    // If the previous operation didn't complete, we should have trailing blocks!
                    Debug.Assert(_currentBlock < totalBlockCount - 1);

                    _currentBlock++;
                    _currentBlockPosition = 0;
                }
                else
                    break;
            }

            // Move to the next block if we're at the end of the current block
            if (_currentBlockPosition == _blockList[_currentBlock].Length && _currentBlock < totalBlockCount - 1)
            {
                AdvanceBlock();
            }

            _currentLine++;
            return true;
        }

        /// <summary>
        /// Moves the current pointer to the start of the previous line
        /// </summary>
        private bool MoveToPreviousLine()
        {
            if (_currentPosition == 0)
                return false;

            // Step 1: Move to the start of the current line
            MoveToStartOfLine();

            if (_currentBlockPosition == 0)
            {
                if (_currentBlock == 0)
                    return false;

                // Move to the previous block
                RetreatBlock();
            }

            // Validate
            Debug.Assert(_currentBlockPosition > 0);

            TextBlock textBlock = _blockList[_currentBlock];
            Debug.Assert(textBlock.Length > 0);

            // Step 2: Move to the end of the previous line
            MoveResult result = textBlock.MoveToPreviousLine(_currentBlockPosition);
            _currentLine--;

            _currentPosition += (result.result - _currentBlockPosition);
            _currentBlockPosition = result.result;

            // Step 3: Now, move to the start of the previous line
            MoveToStartOfLine();

            return true;
        }

        /// <summary>
        /// Moves the current position to the start of the current line
        /// </summary>
        private void MoveToStartOfLine()
        {
            if (_currentPosition == 0)
                return;

            while (true)
            {
                TextBlock textBlock = _blockList[_currentBlock];

                MoveResult result = textBlock.MoveToStartOfLine(_currentBlockPosition);
                _currentPosition += (result.result - _currentBlockPosition);
                _currentBlockPosition = result.result;

                if (!result.complete &&
                    _currentBlock > 0)
                {
                    _currentBlock--;
                    _currentBlockPosition = _blockList[_currentBlock].Length;
                }
                else
                    break;
            }
        }

        /// <summary>
        /// Commits a change list to the current buffer.  This method is called back from the ITextEdit 
        /// implementation
        /// </summary>
        private void ApplyChanges(ITextEdit source, List<TextChange> changes, ChangeNode originNode)
        {
            List<SplitDeletion> splitDeletions = null;
            ChangeNode beforeChangeNode = _currentChange;

            foreach (TextChange change in changes)
            {
                bool ignore = false;

                for (ITextVersion v = originNode; v.Next != null; v = v.Next)
                {
                    TextChangeTransform transform = change.Transform(v.Change);

                    if (transform.Ignore)
                    {
                        ignore = true;
                        break;
                    }

                    if (transform.NewDeletion != null)
                    {
                        if (splitDeletions == null)
                            splitDeletions = new List<SplitDeletion>();

                        splitDeletions.Add(new SplitDeletion(v.Next,
                                                               transform.NewDeletion));
                    }
                }

                if (!ignore)
                    ApplyChange(change);
            }

            // If there were any split deletions, apply them as well (they, in turn,
            // could potentially be split as well, of course...)
            while (splitDeletions != null)
            {
                List<SplitDeletion> currentSplits = splitDeletions;

                splitDeletions = null;
                foreach (SplitDeletion del in currentSplits)
                {
                    bool ignore = false;

                    for (ITextVersion v = del.UpdateStart; v.Next != null; v = v.Next)
                    {
                        TextChangeTransform transform =
                                            del.NewDeletion.Transform(v.Change);

                        if (transform.Ignore)
                        {
                            ignore = true;
                            break;
                        }

                        if (transform.NewDeletion != null)
                        {
                            if (splitDeletions == null)
                                splitDeletions = new List<SplitDeletion>();

                            splitDeletions.Add(new SplitDeletion(v.Next,
                                                                   transform.NewDeletion));
                        }
                    }

                    if (!ignore)
                        ApplyChange(del.NewDeletion);
                }
            }

            // We take the liberty of not firing a change if nothing happened to the buffer.  So, if someone
            // Created a TextEdit and committed it without making any changes, no event is fired.
            if (beforeChangeNode != _currentChange)
                FireChangedEvent(source, beforeChangeNode);
        }

        /// <summary>
        /// Commits one specific change to the buffer
        /// </summary>
        private void ApplyChange(TextChange change)
        {
            bool resetCachedPositions = false;
            bool updateLineCount = false;

            // Deletion
            if (change.OldText.Length > 0)
            {
                int changeLineCount = LinkBuffer.ScanForLineCount(change.OldText);
                // Determine if we need to update the buffer's entire line count, after the insertion operation
                // There are three instance where this could happen.
                // 
                // Case 1: When an deletion happens in between a '\r' and a '\n'
                // Case 2: When an deletion happens after '\r' and the deleted text starts with '\n'
                // Case 3: When an deletion happens before a '\n' and the deleted text ends with '\r'
                if (this.Length > 0)
                {
                    // Since checking for these three cases is going to make the code look uglier, we'll relax
                    // these criteria to the following:
                    //
                    // Case 1: When the character before deletion is a '\r'
                    // Case 2: When the last deleted character is a '\r'
                    if (change.Position > 0)
                    {
                        string prefix = this.GetText(change.Position - 1, 1);
                        if (prefix == "\r")
                            updateLineCount = true;
                    }

                    if (!updateLineCount && change.OldText[change.OldText.Length - 1] == '\r')
                        updateLineCount = true;
                }

                MoveToPosition(change.Position);

                int deletionPoint = _currentBlockPosition;

                // Check if we can insert all the text in the current text block
                int blockNumber = _currentBlock;
                int totalToDelete = change.OldText.Length;

                while (totalToDelete > 0)
                {
                    // Our block number should/will always be less than Block List count
                    Debug.Assert(blockNumber < _blockList.Count);

                    TextBlock block = _blockList[blockNumber];
                    int charsToDelete = Math.Min(block.Length - deletionPoint, totalToDelete);
                    block.DeleteText(deletionPoint, charsToDelete);
                    totalToDelete -= charsToDelete;

                    if (block.Length == 0 && _blockList.Count > 1)
                    {
                        // We've deleted everything from the current block.  So, let's remove it
                        _blockList.RemoveAt(blockNumber);

                        // If after we remove the current block, there's no more blocks left to the right of 
                        // this, we should put the current pointer to the previous block
                        if (blockNumber == _blockList.Count)
                            resetCachedPositions = true;

                        blockNumber--;
                    }
                    else
                    {
                        // Some lines were deleted.  Update the line count
                        block.ComputeLineCount();
                    }

                    if (totalToDelete > 0)
                    {
                        // The deletion point for the next buffer would be from the start
                        blockNumber++;
                        deletionPoint = 0;
                    }
                }

                if (blockNumber > _currentBlock)
                {
                    // The right block is always adjacent to the left
                    Debug.Assert(blockNumber == _currentBlock + 1);

                    // More than 1 block were affected.  We need to merge the extreme two nodes
                    TextBlock leftBlock = _blockList[_currentBlock];
                    TextBlock rightBlock = _blockList[blockNumber];

                    leftBlock.InsertText(leftBlock.Length, rightBlock.GetText(0, rightBlock.Length));
                    leftBlock.WholeLineCount += rightBlock.WholeLineCount;

                    // Now, that we've merged the right block with the left block, remove the right block from 
                    // the list
                    _blockList.Remove(rightBlock);

                    // Now rearrange the blocks.  
                    // Note: The current cache will be reset by this call.  Do not use any of the values
                    // after this call
                    SplitOrMerge(_currentBlock);
                }

                _lineCount -= changeLineCount;
                _length -= change.OldText.Length;

                // Update the line count if needed
                if (updateLineCount)
                {
                    UpdateBufferLineCount();
                    updateLineCount = false;
                }

                // Reset the cached positions if needed
                if (resetCachedPositions)
                    ResetCachedPositions();
            }

            // Insertion
            if (change.NewText.Length > 0)
            {
                int changeLineCount = LinkBuffer.ScanForLineCount(change.NewText);

                // Determine if we need to update the buffer's entire line count, after the insertion operation
                // There are three instance where this could happen.
                // 
                // Case 1: When an insertion happens in between a '\r' and a '\n'
                // Case 2: When an insertion happens after '\r' and the inserted text starts with '\n'
                // Case 3: When an insertion happens before a '\n' and the inserted text ends with '\r'
                if (this.Length > 0)
                {
                    if (change.Position == 0)
                    {
                        // We need to check only the  third case
                        string suffix = this.GetText(0, 1);
                        if (suffix == "\n" && change.NewText[change.NewText.Length - 1] == '\r')
                            updateLineCount = true;
                    }
                    else if (change.Position == this.Length)
                    {
                        // We need to check only the second case
                        string prefix = this.GetText(this.Length - 1, 1);
                        if (prefix == "\r" && change.NewText[0] == '\n')
                            updateLineCount = true;
                    }
                    else
                    {
                        // We need to check all three cases
                        Debug.Assert(this.Length > 1);

                        // Ha ha!  It's a Pre-Suffix!
                        string presuffix = this.GetText(change.Position - 1, 2);
                        if (presuffix[0] == '\r')
                        {
                            if (presuffix[1] == '\n' || change.NewText[0] == '\n')
                                updateLineCount = true;
                        }
                        else if (presuffix[1] == '\n')
                        {
                            if (change.NewText[change.NewText.Length - 1] == '\r')
                                updateLineCount = true;
                        }
                    }
                }

                int insertionPoint;

                // Special case for append
                if (change.Position == this.Length)
                {
                    _currentBlock = _blockList.Count - 1;
                    _currentLine = this.LineCount - 1;
                    insertionPoint = _currentBlockPosition = _blockList[_currentBlock].Length;
                    _currentPosition = this.Length;
                }
                else
                {
                    MoveToPosition(change.Position);
                    insertionPoint = _currentBlockPosition;
                }

                // Check if we can insert all the text in the current text block
                TextBlock block = _blockList[_currentBlock];

                block.InsertText(insertionPoint, change.NewText);
                if (updateLineCount)
                    block.ComputeLineCount();
                else
                    block.WholeLineCount += changeLineCount;
                _lineCount += changeLineCount;

                // Check if we crossed boundaries. 
                // Note: The current cache will be reset by this call.  Do not use any of the values
                // after this call
                SplitOrMerge(_currentBlock);

                _length += change.NewText.Length;

                // Update the line count if needed
                if (updateLineCount)
                {
                    UpdateBufferLineCount();
                    updateLineCount = false;
                }
            }

            _currentChange = _currentChange.CreateNext(change);
        }

        /// <summary>
        /// Fires a Text Change event
        /// </summary>
        private void FireChangedEvent(ITextEdit source, ChangeNode priorChange)
        {
            if (Changed != null)
                Changed(this, new TextChangedEventArgs(source, priorChange));
        }

        /// <summary>
        /// In cases where the buffer line count cannot be reliably computed from the change that happens on 
        /// the buffer, we need to update the buffer line count, based on the blocks
        /// </summary>
        private void UpdateBufferLineCount()
        {
            _lineCount = 1;
            for (int i = _blockList.Count - 1; i >= 0; i--)
                _lineCount += _blockList[i].WholeLineCount;

            // Line count update means, the current positions don't make any sense.
            _currentPosition = _currentLine = _currentBlock = _currentBlockPosition = 0;
        }

        /// <summary>
        /// Splits a text block into two or merges it with the next block.
        /// </summary>
        /// <remarks>
        /// This method should be called only when a block requires a split.  This means, that a block
        /// has more than one line and exceeds the maximum threshold
        /// </remarks>
        private void SplitOrMerge(int blockNumber)
        {
            TextBlock block = _blockList[blockNumber];

            if (!block.NeedsSplit)
                return;

            // Our current positions would have been rendered invalid. 
            // Note: This method will be called only when a change that affected a number of blocks had
            // happened.  Hence, we should reset the current cache!
            _currentPosition = _currentLine = _currentBlock = _currentBlockPosition = 0;

            // If the current block is the last in the list, we will create a new block, or else, we'll
            // take a reference to the next block
            TextBlock nextBlock = null;
            if (blockNumber == _blockList.Count - 1)
            {
                nextBlock = new TextBlock();
                _blockList.Add(nextBlock);
            }
            else
            {
                nextBlock = _blockList[blockNumber + 1];
            }

            while (block.NeedsSplit)
            {
                // Since we don't split '\r' & '\n', we make the minimum available space to do a merge as
                // 2 characters!
                if (nextBlock.AvailableSpace <= 1)
                {
                    // The next block doesn't have enough space for the merge
                    nextBlock = new TextBlock();
                    _blockList.Insert(blockNumber + 1, nextBlock);
                }

                string strippedText = null;

                // Strip as much text as possible
                int lengthStripped = block.StripText(nextBlock.AvailableSpace, out strippedText);

                // Validate the amount stripped.  (The length stripped can be one more than the available
                // space - to account for keeping '\r\n' together)
                Debug.Assert(lengthStripped <= nextBlock.AvailableSpace + 1);

                // Validate the remaining space
                Debug.Assert(block.Length > 0);

                int linesStripped = ScanForLineCount(strippedText);

                // Insert the lines stripped
                nextBlock.InsertText(0, strippedText);
                nextBlock.WholeLineCount += linesStripped;

                // Update the current block
                block.WholeLineCount -= linesStripped;
            }
        }

        /// <summary>
        /// Resets the cached positions so that any move will be an absolute move
        /// </summary>
        private void ResetCachedPositions()
        {
            _currentPosition = _currentBlockPosition = _currentBlock = _currentLine = 0;
        }

        /// <summary>
        /// Move the block to the next one in the list
        /// </summary>
        private void AdvanceBlock()
        {
            // Validate
            Debug.Assert(_currentBlock < _blockList.Count);

            _currentBlock++;
            _currentBlockPosition = 0;
        }

        /// <summary>
        /// Moves the block to the previous one in the list
        /// </summary>
        private void RetreatBlock()
        {
            // Validate
            Debug.Assert(_currentBlock > 0);

            _currentBlock--;
            _currentBlockPosition = _blockList[_currentBlock].Length;
        }


        #endregion

        #region User Data

        public override object this[object key] {
            get {
                object value;
                if (_values != null && _values.TryGetValue(key, out value))
                    return value;
                return null;
            }
            set {
                if (_values == null)
                    _values = new Dictionary<object, object>();
                _values[key] = value;
            }
        }

        #endregion
        #region Static Helpers

        /// <summary>
        /// A utility function that returns the number of lines in a given block of 
        /// text
        ///
        /// We honor the MAC, UNIX and PC way of line counting.
        /// 
        /// \r, \n and \r\n are considered as valid line breaks.  However, \n\r is not.
        /// </summary>
        static internal int ScanForLineCount(string text)
        {
            int lines = 0;
            int i;
            char c;
            for (i = text.Length - 1; i > 0; i--)
            {
                c = text[i];
                if (c == '\n')
                {
                    // i - 1 will never be less than 0 because of the condition above
                    if (text[i - 1] == '\r')
                        i--;

                    lines++;
                }
                else if (c == '\r')
                {
                    lines++;
                }
            }

            // When we break out of the loop, it could be that i is negative.  In that case, we don't need
            // to perform this check.
            if (i == 0 && (text[0] == '\n' || text[0] == '\r'))
                lines++;

            return lines;
        }

        static internal int ScanForLineCount(char[] chars, int length)
        {
            int lines = 0;
            int i;
            char c;
            for (i = length - 1; i > 0; i--)
            {
                c = chars[i];
                if (c == '\n')
                {
                    // i - 1 will never be less than 0 because of the condition above
                    if (chars[i - 1] == '\r')
                        i--;

                    lines++;
                }
                else if (c == '\r')
                {
                    lines++;
                }
            }

            // When we break out of the loop, it could be that i is negative.  In that case, we don't need
            // to perform this check.
            if (i == 0 && (chars[0] == '\n' || chars[0] == '\r'))
                lines++;

            return lines;
        }

        static internal int ScanForLineCount(StringBuilder sb)
        {
            int lines = 0;
            int i;
            char c;
            for (i = sb.Length - 1; i > 0; i--)
            {
                c = sb[i];
                if (c == '\n')
                {
                    // i - 1 will never be less than 0 because of the condition above
                    if (sb[i - 1] == '\r')
                        i--;

                    lines++;
                }
                else if (c == '\r')
                {
                    lines++;
                }
            }

            // When we break out of the loop, it could be that i is negative.  In that case, we don't need
            // to perform this check.
            if (i == 0 && (sb[0] == '\n' || sb[0] == '\r'))
                lines++;

            return lines;
        }

        #endregion // Static Helpers
    }
}